#include <llvm/Module.h>
#include <llvm/Function.h>
#include <llvm/DerivedTypes.h>
#include <llvm/Constants.h>

#include "RCompilerCommon.h"
#include "RCompiler.h"

#include "bc.h"
#include "util.h"

#define STACK_SYMBOLS_DEF(name)\
extern "C" const char* R__##name##Stack__pop;\
extern "C" const char* R__##name##Stack__set;\
extern "C" const char* R__##name##Stack__peek;\
extern "C" const char* R__##name##Stack__push;\
extern "C" const char* R__##name##Stack__size

STACK_SYMBOLS_DEF(Any);
STACK_SYMBOLS_DEF(Int);
STACK_SYMBOLS_DEF(Call);

#undef STACK_SYMBOLS_DEF

extern "C" const char* R__Evaluator__map;
extern "C" const char* R__Evaluator__get;
extern "C" const char* R__Evaluator__getAnyStack;

extern "C" const char* R__AnyStack__JITPop;
extern "C" const char* R__AnyStack__JITPush;
extern "C" const char* R__Evaluator__JITStackFinalize;
extern "C" const char* R__Evaluator__jit_CALLBUILTIN;
extern "C" const char* R__Evaluator__jit_CALLSPECIAL;
extern "C" const char* R__Evaluator__jit_GETVAR;
extern "C" const char* R__Evaluator__jit_CALL;

namespace R {

	RCompilerCommon::RCompilerCommon(llvm::Module* selfModule) {
		this->selfModule = selfModule;

		evalFunctions = new llvm::Function*[BC_LAST]();
		
		llvm::GlobalValue* gv = selfModule->getNamedGlobal(R__Evaluator__map);
		rerror_on(!gv, "unable to find the Evaluator::EvalMap array (%s)", R__Evaluator__map);

		llvm::ConstantArray* initializer = llvm::dyn_cast<llvm::ConstantArray>(llvm::dyn_cast<llvm::GlobalVariable>(gv)->getInitializer());

		uint32_t i = 0;
		for(llvm::ConstantArray::op_iterator it=initializer->op_begin(); it!=initializer->op_end(); it++, i++) {
			llvm::ConstantStruct* e = llvm::dyn_cast<llvm::ConstantStruct>(it->get());
			if(e) {
				evalFunctions[i] = llvm::dyn_cast<llvm::Function>(llvm::dyn_cast<llvm::ConstantExpr>(e->getOperand(0))->getOperand(0));
			}
		}

		patchMap();

		anyPop  = loadFunctionAndCheck(R__AnyStack__pop);
		anySet  = loadFunctionAndCheck(R__AnyStack__set);
		anyPush = loadFunctionAndCheck(R__AnyStack__push);
		anyPeek = loadFunctionAndCheck(R__AnyStack__peek);
		anySize = loadFunctionAndCheck(R__AnyStack__size, false);

		intPop  = loadFunctionAndCheck(R__IntStack__pop);
		intSet  = loadFunctionAndCheck(R__IntStack__set);
		intPush = loadFunctionAndCheck(R__IntStack__push);
		intPeek = loadFunctionAndCheck(R__IntStack__peek);

//		callPop  = loadFunctionAndCheck(R__CallStack__pop);
//		callPush = loadFunctionAndCheck(R__CallStack__push);

	  getEvaluator = loadFunctionAndCheck(R__Evaluator__get);
	  getStack = loadFunctionAndCheck(R__Evaluator__getAnyStack, false);
	  JITPop = loadFunctionAndCheck(R__AnyStack__JITPop, true, false);
	  JITPush = loadFunctionAndCheck(R__AnyStack__JITPush, true, false);
	}

	void RCompilerCommon::patchMap() {
		llvm::Function* jitStackFinalizer = loadFunctionAndCheck(R__Evaluator__JITStackFinalize, false);
		llvm::Function* callBuiltin = loadFunctionAndCheck(R__Evaluator__jit_CALLBUILTIN, false);
		llvm::Function* callSpecial = loadFunctionAndCheck(R__Evaluator__jit_CALLSPECIAL, false);
		llvm::Function* getvar = loadFunctionAndCheck(R__Evaluator__jit_GETVAR, false);
		llvm::Function* call = loadFunctionAndCheck(R__Evaluator__jit_CALL, false);

		evalFunctions[BC_RETURNJMP] = jitStackFinalizer;
		evalFunctions[BC_RETURN] = jitStackFinalizer;
		evalFunctions[BC_CALLBUILTIN] = callBuiltin;
		evalFunctions[BC_CALLSPECIAL] = callSpecial;
		evalFunctions[BC_GETVAR] = getvar;
		evalFunctions[BC_GETVAR_MISSOK] = getvar;
		evalFunctions[BC_CALL] = call;
	}

	RCompilerCommon::~RCompilerCommon() {
		if(evalFunctions)
			delete[] evalFunctions;
	}

	llvm::Function* RCompilerCommon::loadFunctionAndCheck(const char* name, bool intrinsic, bool doesNotAccessMemory) {
		llvm::Function* res = selfModule->getFunction(name);
		rerror_on(!res, "unable to resolve function: %s", name);
		if(intrinsic){
			res = makeIntrinsic(res, doesNotAccessMemory);
		} else {
			res->addFnAttr(llvm::Attribute::AlwaysInline);
		}
		return res;
	}

	llvm::Function* RCompilerCommon::makeIntrinsic(llvm::Function* func, bool doesNotAccessMemory) {
		rerror_on(!func, "unable to find intrinsic");
		if(doesNotAccessMemory)
			func->setDoesNotAccessMemory();
		func->addFnAttr(llvm::Attribute::NoInline);
		return func;
	}
}
